local function SecureCmdOptionEval(val, desired)
	if ( desired ) then
		return (val == desired);
	else
		return (val > 0);
	end
end

local SecureCmdOptionHandlers = {
	-- The rule of thumb for conditionals here is:
	-- 1. The state significantly changes what spells are available.
	-- 2. The player generally has control over the current state.
	-- 3. The designers give it the thumbs up. :)

	combat = function(target, desired)
		return SecureCmdOptionEval(UnitAffectingCombat("player") or UnitAffectingCombat("pet") or 0, tonumber(desired));
	end,

	exists = function(target, desired)
		return SecureCmdOptionEval(UnitExists(target) or 0, tonumber(desired));
	end,

	help = function(target, desired)
		return SecureCmdOptionEval(UnitCanAssist("player",target) or 0, tonumber(desired));
	end,

	harm = function(target, desired)
		return SecureCmdOptionEval(UnitCanAttack("player",target) or 0, tonumber(desired));
	end,

	party = function(target, desired)
		return SecureCmdOptionEval(UnitPlayerOrPetInParty(target) or 0, tonumber(desired));
	end,

	raid = function(target, desired)
		return SecureCmdOptionEval(UnitPlayerOrPetInParty(target) or UnitPlayerOrPetInRaid(target) or 0, tonumber(desired));
	end,

	dead = function(target, desired)
		return SecureCmdOptionEval(UnitIsDead(target) or UnitIsGhost(target) or 0, tonumber(desired));
	end,

	flyable = function(target, desired)
		return SecureCmdOptionEval(IsFlyableArea() or 0, tonumber(desired));
	end,

	indoors = function(target, desired)
		return SecureCmdOptionEval(IsIndoors() or 0, tonumber(desired));
	end,

	outdoors = function(target, desired)
		return SecureCmdOptionEval(IsOutdoors() or 0, tonumber(desired));
	end,

	swimming = function(target, desired)
		return SecureCmdOptionEval(IsSwimming() or 0, tonumber(desired));
	end,

	flying = function(target, desired)
		return SecureCmdOptionEval(IsFlying() or 0, tonumber(desired));
	end,

	mounted = function(target, desired)
		return SecureCmdOptionEval(IsMounted() or 0, tonumber(desired));
	end,

	stealth = function(target, desired)
		return SecureCmdOptionEval(IsStealthed() or 0, tonumber(desired));
	end,

	group = function(target, ...)
		local n = select("#", ...);
		if ( n > 0 ) then
			for i=1, n do
				local desired = strlower((select(i,...)));
				if ( desired == "party" ) then
					if ( GetNumPartyMembers() > 0 ) then
						return true;
					end
				elseif ( desired == "raid" ) then
					if ( GetNumRaidMembers() > 0 ) then
						return true;
					end
				end
			end
		else
			return (GetNumPartyMembers() > 0) or (GetNumRaidMembers() > 0);
		end
	end,

	stance = function(target, ...)
		local stance = GetShapeshiftForm(true);
		local n = select("#", ...);
		if ( n > 0 ) then
			for i=1, n do
				local desired = tonumber((select(i,...)));
				if ( desired == stance ) then
					return true;
				end
			end
		else
			return (stance > 0);
		end
	end,

	pet = function(target, ...)
		if ( not UnitExists("pet") ) then
			return false;
		end
		local n = select("#", ...);
		if ( n > 0 ) then
			local name = UnitName("pet");
			local family = UnitCreatureFamily("pet") or "";
			name, family = strlower(name), strlower(family);
			for i=1, n do
				local desired = strlower((select(i, ...)));
				if ( desired == name or desired == family ) then
					return true;
				end
			end
		else
			return true;
		end
	end,

	modifier = function(target, ...)
		local n = select("#", ...);
		if ( n > 0 ) then
			for i=1, n do
				local key = strlower((select(i,...)));
				if ( (key == "shift" and IsShiftKeyDown()) or
				     (key == "ctrl" and IsControlKeyDown()) or
				     (key == "alt" and IsAltKeyDown()) ) then
					return true;
				end
			end
		else
			return IsModifierKeyDown();
		end
	end,

	button = function(target, ...)
		local clickbutton = GetMouseButtonClicked();
		local macrobutton = GetRunningMacroButton();
		if ( not clickbutton and not macrobutton ) then
			-- Useful for macros checking [button:1] for dynamic feedback
			clickbutton = "LeftButton";
		end
		for i=1, select("#", ...) do
			local desired = GetMouseButtonName((select(i,...)));
			if ( desired == clickbutton or
			     desired == macrobutton ) then
				return true;
			end
		end
	end,

	actionbar = function(target, ...)
		local page = GetActionBarPage();
		for i=1, select("#", ...) do
			local desired = tonumber((select(i,...)));
			if ( desired == page ) then
				return true;
			end
		end
	end,

	equipped = function(target, ...)
		for i=1, select("#", ...) do
			local type = select(i, ...);
			if ( IsEquippedItemType(type) ) then
				return true;
			end
		end
	end,

	channeling = function(target, ...)
		local spell = UnitChannelInfo("player");
		if ( not spell ) then
			return false;
		end
		spell = strlower(spell);
		local n = select("#", ...);
		if ( n > 0 ) then
			for i=1, n do
				local desired = strlower((select(i, ...)));
				if ( desired == spell ) then
					return true;
				end
			end
		else
			return true;
		end
	end,
}

local function FillOptionTable(o, ...)
	for i=1, select("#",...) do
		local option = strtrim((select(i,...)));
		if ( option ~= "" ) then
			local invert = false;
			local args;
			option, args = strsplit(':', option);
			option = strlower(option);
			if ( strmatch(option, "^no") ) then
				invert = true;
				option = strsub(option, 3);
			end
			local handler = SecureCmdOptionHandlers[option];
			if ( handler ) then
				if ( args ) then
					if ( strfind(args, '/', 1, true) ) then
						if ( invert ) then
							tinsert(o, function(target)
								return not handler(target, strsplit('/', args));
							end);
						else
							tinsert(o, function(target)
								return handler(target, strsplit('/', args));
							end);
						end
					else
						if ( invert ) then
							tinsert(o, function(target)
								return not handler(target, args);
							end);
						else
							tinsert(o, function(target)
								return handler(target, args);
							end);
						end
					end
				else
					if ( invert ) then
						tinsert(o, function(target)
							return not handler(target);
						end);
					else
						tinsert(o, handler);
					end
				end
			else
				local info = ChatTypeInfo["SYSTEM"];
				DEFAULT_CHAT_FRAME:AddMessage(format(ERR_UNKNOWN_MACRO_OPTION, option), info.r, info.g, info.b, info.id);
			end
		end
	end
end

local function BuildOptionSetCache(tbl, optionSet)
	local t = {};
	for options in gmatch(optionSet, "%b[]") do
		local o = {}
		local target;
		options = strtrim(strsub(options,2,-2));
		local s = strmatch(options, "target=[^,]+");
		if ( s ) then
			options = gsub(options, "target=[^,]+,?", "");
			target = strsub(s, 8);
		end
		FillOptionTable(o, strsplit(',', options));
		o.target = target;
		tinsert(t, o);
	end
	tbl[optionSet] = t;
	return t;
end
local SecureOptionSetCache = setmetatable({}, {
	__index = BuildOptionSetCache
})
local OptionSetCache = setmetatable({}, {
	__index = BuildOptionSetCache
})

function SecureCmdOptionParseArgs(...)
	for i=1, select("#",...) do
		local cmd = select(i,...);
		local _, start, options = strfind(cmd, "(%[.*%])");
		local action = strsub(cmd, (start or 0)+1);
		local valid = true;
		local target;

		if ( options ) then
			local set;
			if ( issecure() ) then
				set = SecureOptionSetCache[options];
			else
				set = OptionSetCache[options];
			end
			local i = 1;

			valid = false;
			while ( not valid and i <= #set ) do
				local o = set[i];
				local j = 1;
				target = o.target;
				valid = true;
				while ( valid and j <= #o ) do
					valid = o[j](target or "target");
					j = j + 1;
				end
				i = i + 1;
			end
		end
		if ( valid ) then
			return strtrim(action), target;
		end
	end
end

function SecureCmdOptionParse(args)
	return SecureCmdOptionParseArgs(strsplit(';', args));
end

